//
// win_console.c - Dedicated console
//


#include "win_local.h"


#define CONSOLE_WINDOW_NAME			"Vortex Console"
#define	CONSOLE_WINDOW_CLASS		"VortexCon"
#define CONSOLE_WINDOW_STYLE		(WS_OVERLAPPED | WS_BORDER | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX)

#define MAX_OUTPUT					32768
#define MAX_INPUT					256

struct winConsole_t
{
	int			outputLength;			// To keep track of output buffer length
	char		inputBuffer[MAX_INPUT];	// Buffered input from dedicated console

	bool		onError;				// If true, we're on a fatal error
	bool		flashError;				// If true, flash error message to red

	// Window stuff
	HWND		hWnd;
	HWND		hWndError;
	HWND		hWndOutput;
	HWND		hWndInput;
	HWND		hWndCopy;
	HWND		hWndClear;
	HWND		hWndQuit;

	HFONT		hFont;
	HFONT		hFontBold;

	HBRUSH		hBrush;
	HBRUSH		hBrushGray;

	WNDPROC		defInputProc;
};

static winConsole_t	winCon;


/*
 ==============================================================================

    WINDOW PROCEDURE

 ==============================================================================
*/


/*
 ==================
 Win_ConsoleWindowProc
 ==================
*/
static LRESULT CALLBACK Win_ConsoleWindowProc (__in HWND hWnd, __in UINT uMsg, __in WPARAM wParam, __in LPARAM lParam)
{
	switch (uMsg) {
	case WM_CREATE:
		winMain.hWndConsole = hWnd;

		break;
	case WM_DESTROY:
		winMain.hWndConsole = NULL;

		break;
	case WM_ACTIVATE:
		if (LOWORD(wParam) != WA_INACTIVE)
		{
			SetFocus(winCon.hWndInput);
			return 0;
		}

		break;
	case WM_CLOSE:
		PostQuitMessage(0);

		break;
	case WM_COMMAND:
		if (HIWORD(wParam) == BN_CLICKED)
		{
			if ((HWND)lParam == winCon.hWndCopy)
			{
				SendMessage(winCon.hWndOutput, EM_SETSEL, 0, -1);
				SendMessage(winCon.hWndOutput, WM_COPY, 0, 0);

				break;
			}

			if ((HWND)lParam == winCon.hWndClear)
			{
				SendMessage(winCon.hWndOutput, EM_SETSEL, 0, -1);
				SendMessage(winCon.hWndOutput, EM_REPLACESEL, FALSE, (LPARAM)"");

				break;
			}

			if ((HWND)lParam == winCon.hWndQuit)
				PostQuitMessage(0);
		}

		break;
	case WM_CTLCOLORSTATIC:
		if ((HWND)lParam == winCon.hWndError)
		{
			if (winCon.flashError)
			{
				SetBkColor((HDC)wParam, RGB(128, 128, 128));
				SetTextColor((HDC)wParam, RGB(255, 0, 0));
			} else
			{
				SetBkColor((HDC)wParam, RGB(128, 128, 128));
				SetTextColor((HDC)wParam, RGB(0, 0, 0));
			}

			return (LONG)winCon.hBrushGray;
		}

		if ((HWND)lParam == winCon.hWndOutput)
		{
			SetBkColor((HDC)wParam, RGB(54, 66, 83));
			SetTextColor((HDC)wParam, RGB(255, 255, 255));

			return (LONG)winCon.hBrush;
		}

		break;
	case WM_TIMER:
		if (winCon.onError)
		{
			winCon.flashError = !winCon.flashError;

			InvalidateRect(winCon.hWndError, NULL, FALSE);
		}

		break;
	}

	// Pass all unhandled messages to DefWindowProc
	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

/*
 ==================
 Win_ConsoleInputProc
 ==================
*/
static LRESULT CALLBACK Win_ConsoleInputProc (__in HWND hWnd, __in UINT uMsg, __in WPARAM wParam, __in LPARAM lParam)
{
	char    text[MAX_INPUT];

	switch (uMsg){
	case WM_CHAR:
		if (wParam == VK_RETURN)
		{
			if (GetWindowText(winCon.hWndInput, text, sizeof(text)))
			{
				SetWindowText(winCon.hWndInput, "");

				Com_Printf("]%s\n", text);

				Str_Append(winCon.inputBuffer, text, sizeof(winCon.inputBuffer));
				Str_Append(winCon.inputBuffer, "\n", sizeof(winCon.inputBuffer));
			}

			return 0;   // Keeps it from beeping
		}

		break;
	}

	// Pass all unhandled messages to DefWindowProc
	return CallWindowProc(winCon.defInputProc, hWnd, uMsg, wParam, lParam);
}


/*
 ==============================================================================

    WINDOW CREATION

 ==============================================================================
*/


/*
 ==================
 Win_CreateConsole
 ==================
*/
void Win_CreateConsole ()
{
	WNDCLASSEX   wndClass;
	RECT		 rect;
	int			 screenWidth, screenHeight;
	int			 x, y, w, h;

	// Calculate window position and dimensions
	screenWidth = GetSystemMetrics(SM_CXSCREEN);
	screenHeight = GetSystemMetrics(SM_CYSCREEN);

	rect.left = (screenWidth - 540) / 2;
	rect.top = (screenHeight - 450) / 2;
	rect.right = rect.left + 540;
	rect.bottom = rect.top + 450;

	AdjustWindowRectEx(&rect, CONSOLE_WINDOW_STYLE, FALSE, 0);

	x = rect.left;
	y = rect.top;
	w = rect.right - rect.left;
	h = rect.bottom - rect.top;

	// Register the window class
	wndClass.cbSize = sizeof(WNDCLASSEX);
	wndClass.style = 0;
	wndClass.lpfnWndProc = Win_ConsoleWindowProc;
	wndClass.cbClsExtra = 0;
	wndClass.cbWndExtra = 0;
	wndClass.hInstance = winMain.hInstance;
	wndClass.hIcon = LoadIcon(winMain.hInstance, MAKEINTRESOURCE(IDI_ICON1));
	wndClass.hIconSm = NULL;
	wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
	wndClass.lpszMenuName = NULL;
	wndClass.lpszClassName = CONSOLE_WINDOW_CLASS;

	if (!RegisterClassEx(&wndClass))
	{
		MessageBox(NULL, "Could not register console window class", "ERROR", MB_OK | MB_ICONERROR | MB_TASKMODAL);
		exit(0);
	}

	// Create the window
	winCon.hWnd = CreateWindowEx(0, CONSOLE_WINDOW_CLASS, CONSOLE_WINDOW_NAME, CONSOLE_WINDOW_STYLE, x, y, w, h, NULL, NULL, winMain.hInstance, NULL);
	if (!winCon.hWnd)
	{
		UnregisterClass(CONSOLE_WINDOW_CLASS, winMain.hInstance);

		MessageBox(NULL, "Could not create console window", "ERROR", MB_OK | MB_ICONERROR | MB_TASKMODAL);
		exit(0);
	}

	// Create the controls
	winCon.hWndError = CreateWindowEx(0, "STATIC", "", WS_CHILD | SS_SUNKEN | SS_LEFT, 5, 5, 530, 30, winCon.hWnd, NULL, winMain.hInstance, NULL);
	winCon.hWndOutput = CreateWindowEx(0, "EDIT", "", WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL | ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY, 5, 40, 530, 350, winCon.hWnd, NULL, winMain.hInstance, NULL);
	winCon.hWndInput = CreateWindowEx(0, "EDIT", "", WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT | ES_AUTOHSCROLL, 5, 395, 530, 20, winCon.hWnd, NULL, winMain.hInstance, NULL);
	winCon.hWndCopy = CreateWindowEx(0, "BUTTON", "copy", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON | BS_CENTER | BS_VCENTER, 5, 420, 70, 25, winCon.hWnd, NULL, winMain.hInstance, NULL);
	winCon.hWndClear = CreateWindowEx(0, "BUTTON", "clear", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON | BS_CENTER | BS_VCENTER, 80, 420, 70, 25, winCon.hWnd, NULL, winMain.hInstance, NULL);
	winCon.hWndQuit = CreateWindowEx(0, "BUTTON", "quit", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON | BS_CENTER | BS_VCENTER, 465, 420, 70, 25, winCon.hWnd, NULL, winMain.hInstance, NULL);

	// Create and set the fonts
	winCon.hFont = CreateFont(14, 0, 0, 0, FW_LIGHT, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FIXED_PITCH | FF_MODERN, "Courier New");
	winCon.hFontBold = CreateFont(20, 0, 0, 0, FW_SEMIBOLD, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, "System");

	SendMessage(winCon.hWndError, WM_SETFONT, (WPARAM)winCon.hFont, FALSE);
	SendMessage(winCon.hWndOutput, WM_SETFONT, (WPARAM)winCon.hFont, FALSE);
	SendMessage(winCon.hWndInput, WM_SETFONT, (WPARAM)winCon.hFont, FALSE);
	SendMessage(winCon.hWndCopy, WM_SETFONT, (WPARAM)winCon.hFontBold, FALSE);
	SendMessage(winCon.hWndClear, WM_SETFONT, (WPARAM)winCon.hFontBold, FALSE);
	SendMessage(winCon.hWndQuit, WM_SETFONT, (WPARAM)winCon.hFontBold, FALSE);

	// Create the brushes
	winCon.hBrush = CreateSolidBrush(RGB(54, 66, 83));
	winCon.hBrushGray = CreateSolidBrush(RGB(128, 128, 128));

	// Subclass input edit box
	winCon.defInputProc = (WNDPROC)SetWindowLong(winCon.hWndInput, GWL_WNDPROC, (LONG)Win_ConsoleInputProc);

	// Set a timer for flashing error messages
	SetTimer(winCon.hWnd, 1, 1000, NULL);

	// Set text limit for input edit box
	SendMessage(winCon.hWndInput, EM_SETLIMITTEXT, (WPARAM)(MAX_INPUT - 1), 0);

	// Show it
	ShowWindow(winCon.hWnd, SW_SHOW);
	UpdateWindow(winCon.hWnd);
	SetForegroundWindow(winCon.hWnd);
	SetFocus(winCon.hWndInput);
}


/*
 ==============================================================================

    INPUT, OUTPUT, ERROR AND SHOW WINDOW FUNCTIONS

 ==============================================================================
*/


/*
 ==================
 Win_ConsoleOutput
 ==================
*/
void Win_ConsoleOutput (const char *text)
{
	char	buffer[MAX_PRINT_MESSAGE];
	int		length = 0;

	// Copy into an intermediate buffer
	while (*text && (length < MAX_PRINT_MESSAGE - 2)){
		// Copy \r\n or convert \n\r to \r\n
		if ((*text == '\r' && text[1] == '\n') || (*text == '\n' && text[1] == '\r'))
		{
			buffer[length++] = '\r';
			buffer[length++] = '\n';

			text += 2;
			continue;
		}

		// Convert \n or \r to \r\n
		if (*text == '\n' || *text == '\r')
		{
			buffer[length++] = '\r';
			buffer[length++] = '\n';

			text += 1;
			continue;
		}

		// Ignore color escape sequences
		if (Q_IsColorString(text))
		{
			text += 2;
			continue;
		}

		// Ignore non-printable characters
		if (*text < ' ')
		{
			text += 1;
			continue;
		}

		// Copy the character
		buffer[length++] = *text++;
	}
	buffer[length] = 0;

	// Check for overflow
	winCon.outputLength += length;
	if (winCon.outputLength >= MAX_OUTPUT)
	{
		winCon.outputLength = length;

		SendMessage(winCon.hWndOutput, EM_SETSEL, 0, -1);
	}

	// Scroll down
	SendMessage(winCon.hWndOutput, EM_LINESCROLL, 0, 0xFFFF);
	SendMessage(winCon.hWndOutput, EM_SCROLLCARET, 0, 0);

	// Add the text
	SendMessage(winCon.hWndOutput, EM_REPLACESEL, FALSE, (LPARAM)buffer);

	// Update
	UpdateWindow(winCon.hWnd);
}

/*
 ==================
 Win_ConsoleInput
 ==================
*/
char *Win_ConsoleInput ()
{
	static char   buffer[MAX_INPUT];

	if (!winCon.inputBuffer[0])
		return NULL;

	Str_Copy(buffer, winCon.inputBuffer, sizeof(buffer));
	winCon.inputBuffer[0] = 0;

	return buffer;
}

/*
 ==================
 Win_SetConsoleError
 ==================
*/
void Win_SetConsoleError (const char *text)
{
	winCon.onError = true;

	SetWindowText(winCon.hWndError, text);

	ShowWindow(winCon.hWndError, SW_SHOW);
	ShowWindow(winCon.hWndInput, SW_HIDE);

	ShowWindow(winCon.hWnd, SW_SHOW);
	UpdateWindow(winCon.hWnd);
	SetForegroundWindow(winCon.hWnd);
	SetFocus(winCon.hWnd);
}

/*
 ==================
 Win_ShowConsole
 ==================
*/
void Win_ShowConsole (bool show)
{
	if (!show)
	{
		ShowWindow(winCon.hWnd, SW_HIDE);
		return;
	}

	ShowWindow(winCon.hWnd, SW_SHOW);
	UpdateWindow(winCon.hWnd);
	SetForegroundWindow(winCon.hWnd);
	SetFocus(winCon.hWndInput);
}

/*
 ==================
 Win_Printf
 ==================
*/
void Win_Printf (const char *fmt)
{
	Win_ConsoleOutput(fmt);
}

/*
 ==================
 Win_Error
 ==================
*/
void Win_Error (const char *fmt, ...)
{
	char	message[MAX_PRINT_MESSAGE];
	va_list	argPtr;
	int		length;
	MSG		msg;

	// Make sure all subsystems are down
	Com_Shutdown();

	va_start(argPtr, fmt);
	length = Str_VSPrintf(message, sizeof(message), fmt, argPtr);
	va_end(argPtr);

	// Echo to the console
	Win_Printf("\n");
	Win_Printf(message);
	Win_Printf("\n");

	// Show the error message
	Win_SetConsoleError(message);

	// Wait for the user to quit
	while (1){
		if (!GetMessage(&msg, NULL, 0, 0))
			Win_Exit();

		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
}


/*
 ==============================================================================

    INITIALIZATION AND SHUTDOWN

 ==============================================================================
*/


/*
 ==================
 Win_ShutdownConsole
 ==================
*/
void Win_ShutdownConsole ()
{
	KillTimer(winCon.hWnd, 1);

	if (winCon.defInputProc)
		SetWindowLong(winCon.hWndInput, GWL_WNDPROC, (LONG)winCon.defInputProc);

	if (winCon.hBrush)
		DeleteObject(winCon.hBrush);
	if (winCon.hBrushGray)
		DeleteObject(winCon.hBrushGray);

	if (winCon.hFont)
		DeleteObject(winCon.hFont);
	if (winCon.hFontBold)
		DeleteObject(winCon.hFontBold);

	ShowWindow(winCon.hWnd, SW_HIDE);
	DestroyWindow(winCon.hWnd);
	UnregisterClass(CONSOLE_WINDOW_CLASS, winMain.hInstance);

	memset(&winCon, 0, sizeof(winConsole_t));
}